/**
 * 
 */
package jvs.peer.vfs;

import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import jvs.peer.io.ChannelManager;
import jvs.peer.io.PeerStatus.Status;
import jvs.peer.xmpp.XmppFileUtil;
import jvs.vfs.IFile;
import jvs.vfs.IFileBaseImpl;
import jvs.vfs.sec.AccessControl;
import jvs.vfs.sec.User;
import x.java.io.File.Info;

/**
 * @author qiangli
 * 
 */
public class TildeFileImpl extends IFileBaseImpl {
	private Info info;
	
	private ArrayList<Info> list;

	private String path;
	
	private String peer;

	/**
	 * @param uri
	 */
	public TildeFileImpl(URI uri) {
		super(uri);
		// tilde:/peer/path
		if (uri.getScheme() == null
				|| !uri.getScheme().equalsIgnoreCase("tilde")) {
			throw new IllegalArgumentException("incorrect scheme: "
					+ uri.getScheme());
		}
		parsePeerId(uri.getPath());
	}

	public TildeFileImpl(URI uri, Info info) {
		this(uri);
		this.info = info;
	}

	private TildeFileImpl(String peer) {
		this.peer = peer;
		this.path = null;
		try {
			this.uri = new URI("tilde:/"+peer);
		} catch (URISyntaxException e) {
			e.printStackTrace();
		}
	}
	
	@Override
	public boolean canRead() {
		if (!exists()) {
			return false;
		}
		if (path == null) {
			return true;
		}
		return (info != null) && info.canRead;
	}

	@Override
	public boolean canWrite() {
		if (!exists()) {
			return false;
		}
		if (path == null) {
			return false;
		}
		return (info != null) && info.canWrite;
	}

	@Override
	public boolean create() {
		return false;
	}

	@Override
	public boolean delete() {
		return false;
	}

	@Override
	public boolean exists() {
		// ~/peer/path
		if (peer == null && path == null) {
			return true;
		}
		//full/valid jid required. eg index.htm is not a valid peer id
		if (peer != null && peer.indexOf('@') == -1) {
			return false;
		}
		// remote peer root
		if (path == null) {
			return true;
		}
		//
		loadInfo();
		
		if (info == null) {
			return false;
		}
		return true;
	}

	@Override
	public InputStream getInputStream() {
		try {
			return XmppFileUtil.pullAsStream(peer, path == null ? "/" : path);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	@Override
	public long getLastModified() {
		if (peer == null || path == null) {
			return fs.getUptime();
		}
		loadInfo();
		if (info == null) {
			return 0;
		}
		return info.lastModified;
	}

	@Override
	public long getLength() {
		if (peer == null || path == null) {
			return 0;
		}
		loadInfo();
		if (info == null) {
			return 0;
		}
		return info.length;
	}

	@Override
	public OutputStream getOutputStream() {
		return null;
	}

	@Override
	public boolean isDirectory() {
		// top or peer contact list
		if (peer == null || path == null ) {
			return true;
		}
		//
		loadInfo();
		if (info != null) {
			return info.isDirectory;
		}
		return true;
	}

	@Override
	public boolean isFile() {
		// local? if true, directory only
		if (peer == null || path == null) {
			return false;
		}
		//
		loadInfo();
		if (info != null) {
			return info.isFile;
		}
		return false;
	}

	@Override
	public boolean isLocal() {
		return (peer == null || path == null);
	}

	@Override
	public boolean isNative() {
		return false;
	}

	@Override
	public boolean isEmpty() {
		if (peer == null || path == null) {
			return false;
		}
		return super.isEmpty();
	}

	@Override
	public String[] list() {
		// contact in the address book as children of root
		if (peer == null) {
			return listUsers();
		}
		//
		loadInfo();
		if (info != null && !info.isFile) {
			loadList();
			if (list == null) {
				return new String[0];
			}
			String[] l = new String[list.size()];
			int i=0;
			for (Info fi: list) {
				l[i++] = fi.name;
			}
			return l;
		} 
		return null;
	}

	private String[] listUsers() {
		AccessControl acl = getAccessControl();
		if (acl == null) {
			return null;
		}
		List<User> users = acl.users();
		if (users == null) {
			return new String[0];
		}
		String[] us = new String[users.size()];
		int i = 0;
		for (User u : users) {
			us[i++] = u.getId();
		}
		Arrays.sort(us);
		return us;
	}

	public IFile[] listFiles() {
		if (peer == null) {
			String[] us = listUsers();
			if (us == null) {
				return null;
			}
			int n = us.length;
			IFile[] fs = new IFile[n];
			for (int i = 0; i < n; i++) {
				fs[i] = new TildeFileImpl(us[i]);
			}
			return fs;
		}
		loadInfo();
		if (info != null && !info.isFile) {
			try {
				loadList();
			} catch (Exception e) {
				e.printStackTrace();
			}
			if (list == null) {
				return new IFile[0];
			}
			//
			int n = list.size();
			IFile[] fs = new IFile[n];
			String p = uri.toString();
			p = (p.endsWith("/")? p : p+"/");
			int i = 0;
			for (Info fi: list) {
					try {
						String s = p + URLEncoder.encode(fi.name, "UTF-8");
						fs[i++] = new TildeFileImpl(new URI(s), fi);
					} catch (UnsupportedEncodingException e) {
						e.printStackTrace();
					} catch (URISyntaxException e) {
						e.printStackTrace();
					}
			}
			return fs;
		} 
		return null;
	}
	
	@Override
	public IFile[] listFiles(int off, int max) {
		if (peer == null) {
			String[] l = listUsers();
			if (l == null) {
				return null;
			}
			//
			int len = l.length;
			if (off >= len) {
				return new IFile[0];
			}
			Arrays.sort(l);//
			len = Math.min(len, off + max);
			IFile[] rl = new IFile[len - off];
			for (int i = off; i < len; i++) {
				rl[i - off] = new TildeFileImpl(l[i]);
			}
			return rl;
		}
		//
		loadInfo();
		if (info != null && !info.isFile) {
			try {
				loadList();
			} catch (Exception e) {
				e.printStackTrace();
			}
			if (list == null) {
				return new IFile[0];
			}
			//
			int len = list.size();
			if (off >= len) {
				return new IFile[0];
			}
			//assume sorted
			len = Math.min(len, off + max);
			IFile[] rl = new IFile[len - off];
			String p = uri.toString();
			p = (p.endsWith("/")? p : p+"/");
			for (int i = off; i < len; i++) {
				//try {
					Info fi = list.get(i);
					try {
						String s = p + URLEncoder.encode(fi.name, "UTF-8");
						rl[i - off] = new TildeFileImpl(new URI(s), fi);
					} catch (URISyntaxException e) {
						e.printStackTrace();
					} catch (UnsupportedEncodingException e) {
						e.printStackTrace();
					}
			}
			return rl;
		} 
		return null;
	}

	synchronized private void loadInfo() {
		if (info != null) {
			return;
		}
		try {
			Status st = ChannelManager.getInstance().checkStatus(peer);
			if (st.equals(Status.AVAIL) == false) {
				throw new RuntimeException("Peer not available");
			}
			//
			info = XmppFileUtil.info(peer, path == null ? "/" : path);
		} catch (FileNotFoundException e) {
			//fileNotFound = true;
			info = null;
		} catch (RuntimeException e) {
			info = null;
			e.printStackTrace();
			throw e;
		} catch (Exception e) {
			info = null;
			e.printStackTrace();
			throw new RuntimeException("Can't access remote peer file. "+e);
		}
	}

	synchronized private void loadList() {
		if (list == null) {
			try {
				list = new ArrayList<Info>();
				XmppFileUtil.list(peer, path == null ? "/" : path, 0, Integer.MAX_VALUE, list);
			} catch (Exception e) {
				list = null;
				e.printStackTrace();
				throw new RuntimeException("Failed to list remote file. "+e);
			}
		}
	}

	@Override
	public boolean mkdir() {
		return false;
	}

	@Override
	public boolean move(URI uri) {
		return false;
	}

	@Override
	public void refresh() {
		info = null;
		list = null;
	}

	private void parsePeerId(String s) {
		// /jid/path
		// tilde root
		if (s == null || s.equals("") || s.equals("/")) {
			peer = null;
			path = null;
			return;
		}
		int i2 = s.indexOf("/", 1);
		// top level - peer with path == null
		if (i2 == -1) {
			peer = s.substring(1);
			path = null;
			return;
		}
		// remote file from peer
		peer = s.substring(1, i2);
		path = s.substring(i2);
		if (path.equals("/")) {
			path = null;
		}
	}

	@Override
	public URI toRealURI() {
		return uri;
	}
}
